﻿/*******************************************************************************************
 * Copyright (c) 2006-2009 Alex Robson, Rob Simmons, Craig Israel, and Rachel Twyford
 *
 * Use of this software in any form requires that you have both 
 * read and agreed to the following terms:
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions: 
 *
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, 
 * this list of conditions and the following disclaimer in the 
 * documentation and/or other materials provided with the distribution.
 * Neither the name of Nvigorate nor the names of its contributors may 
 * be used to endorse or promote products derived from this software without 
 * specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
 * DISCLAIMED. IN NO EVENT SHALL ANY CONTRIBUTOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ****************************************************************************************/

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Nvigorate.Common;
using Nvigorate.Common.Reflection;
using Nvigorate.Logging;
using Nvigorate.Relational.Mapping.Fluent;
using Nvigorate.Relational.Mapping.Interface;

namespace Nvigorate.Relational.Mapping.Index
{
    internal class ScanTargetMapLoader : IMapLoader
    {
        #region Fields

        private readonly IEnumerable<ScanTarget> _scanTargets;
        private readonly Type _bindingMapType;
        private readonly IBindingMapLoaderFactory _bindingMapLoaderFactory;
        private readonly IFileHelper _fileHelper;

        #endregion

        #region Constructors

        public ScanTargetMapLoader(IEnumerable<ScanTarget> scanTargets, Type bindingMapType) :
            this(scanTargets, bindingMapType, new BindingMapLoaderFactory())
        {
        }

        internal ScanTargetMapLoader(IEnumerable<ScanTarget> scanTargets, Type bindingMapType,
                                     IBindingMapLoaderFactory bindingMapLoaderFactory) :
                                         this(scanTargets, bindingMapType, bindingMapLoaderFactory, new FileHelper())
        {
        }

        internal ScanTargetMapLoader(IEnumerable<ScanTarget> scanTargets, Type bindingMapType,
                                     IBindingMapLoaderFactory bindingMapLoaderFactory,
                                     IFileHelper fileHelper)
        {
            _scanTargets = scanTargets;
            _bindingMapType = bindingMapType;
            _bindingMapLoaderFactory = bindingMapLoaderFactory;
            _fileHelper = fileHelper;
        }

        #endregion

        #region Public Methods

        public IEnumerable<IBindingMap> GetMaps()
        {
            var maps = new List<IBindingMap>();

            foreach (ScanTarget scanTarget in _scanTargets)
            {
                try
                {
                    maps.AddRange(
                        (scanTarget.Namespace != "")
                               ?
                                   LoadMapsFromNamespace(scanTarget.Namespace)
                               :
                                   LoadMapsFromPath(scanTarget.MapDirectory,
                                                    scanTarget.RelativeTo,
                                                    scanTarget.BindingMapExtension)
                        );
                }
                catch (Exception e)
                {
                    LogManager.LogError(e);
                    Console.WriteLine(e);
                    throw;
                }
            }

            return maps;
        }

        #endregion

        #region Private Methods

        private IEnumerable<IBindingMap> LoadMapsFromNamespace(string targetNamespace)
        {
            List<Assembly> currentAssemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();
            List<string> loadedNames = currentAssemblies.Select(a => a.GetName().Name).ToList();

            var assembliesToLoadFrom = new List<Assembly>();

            foreach (var assembly in currentAssemblies)
            {
                string assemblyName = assembly.GetName().Name;
                if (assemblyName.Contains(targetNamespace) || targetNamespace.Contains(assemblyName))
                {
                    assembliesToLoadFrom.Add(assembly);
                }

                List<AssemblyName> referencedAssemblies = assembly.GetReferencedAssemblies().ToList();
                foreach (
                    AssemblyName referenceName in
                        referencedAssemblies.Where(
                            n => n.Name.Contains(targetNamespace) || targetNamespace.Contains(n.Name)))
                {
                    if (!loadedNames.Contains(referenceName.Name))
                    {
                        Assembly referenceAssembly = Assembly.Load(referenceName);
                        assembliesToLoadFrom.Add(referenceAssembly);
                        loadedNames.Add(referenceName.Name);
                    }
                }
            }

            return assembliesToLoadFrom.SelectMany(a => LoadMapsFromAssembly(a, targetNamespace));
        }

        private IEnumerable<IBindingMap> LoadMapsFromAssembly(Assembly assembly, string nameSpace)
        {
            return assembly
                .GetTypes()
                .Where(t =>
                       t.FullName.Contains(nameSpace) &&
                       Reflector.HasBaseTypeOf(t, typeof(FluentMap<>)))
                .Select(t => _bindingMapLoaderFactory.GetBindingMapLoader(t))
                .Where(l => l != null)
                .Select(l => l.GetMap());
        }

        private IEnumerable<IBindingMap> LoadMapsFromPath(string filePath, StandardPaths parent,
                                                          string bindExtension)
        {
            var filePaths = _fileHelper.GetFileNamesForPath(GetFullpath(parent, filePath),
                                                            string.Concat("*.", bindExtension));
            filePaths.Sort();

            return filePaths.Select(file => _bindingMapLoaderFactory.GetBindingMapLoader(file, _bindingMapType).GetMap());
        }

        private string GetFullpath(StandardPaths parent, string filePath)
        {
            string parentPath;
            switch (parent)
            {
                case StandardPaths.ExecutableRoot:
                    parentPath = Assembly.GetExecutingAssembly().Location;
                    break;
                default:
                    parentPath = "";
                    break;
            }

            return Path.Combine(parentPath ?? "", filePath);
        }

        #endregion
    }
}